/* main.cpp
 * Main Program, initialization, and # command implementation
 * UnderC C++ interpreter
 * Steve Donovan, 2001
 * This is GPL'd software, and the usual disclaimers apply.
 * See LICENCE
 */
#include <ctype.h>
#include <stdio.h>
#include "common.h"

#include "breakpoints.h"
#include "module.h"
#include "tokens.h"
#include "engine.h"
#include "mangle.h"
#include "directcall.h"
#include <time.h>
#include "keywords.h"
#include "operators.h"
#include "uc_tokens.h"
#include "program.h"
#include "utils.h"
#include "input.h"
#include "version.h"
#include "errors.h"
#include "hard_except.h"
#include "main.h"
#include "config.h"
#include "loaded_module_list.h"

#ifdef _WCON
#include "ide.h"
#endif

// *DEBUG*
#ifdef _DEBUG
#include <crtdbg.h>
int check_mem(); //  *DEBUG*

// *add 1.2.8 A debug interface for assisting the poor debuggers of UC
int gDebugBreak = 0;
FBlock* gFunBlock = 0;
void __break(int);
#else
int check_mem() 
{
  return 1;  
}

#endif

void dump_fun_stack(); // in engine.cpp

int yyparse(); // found in TPARSER.CPP (generated by Bison from parser.y)
bool check_error();  // ditto
void clear_global_namespace();  // in table.cpp

void dump_module_traceback(); // in uc_tokens.cpp

#ifdef BUILD_WITH_GTK 
void special_init(int *argc, char ***argv, bool on_off);  // found in UCW_GTK
#else
void special_init(int *argc, char ***argv, bool on_off) { }
#endif

void macro_cleanup(); // in tokens.cpp (backdoor!!)
void add_builtin_macro(const char* name); // in uc_tokens.cpp (ditto, I suppose)

const int EXPR_BUFF_SIZE = 1024, PATHSIZE = 256;

// *hack 1.2.2b The console version experiences a most odd problem
// when running a program occaisionally. yylex() returns 0, but yyparse()
// considers this an error, and gives us a characteristic 'DUD' error message.
// To get around this, the console version shuts down by _throwing an int_
int call_parser()
{
  try {
    if (yyparse()==1) { // an error occured!
       Input::clear();
       return FAIL; 
    } else 
	   return OK; // successful exit!
  } catch(int) {
#ifndef _WCON
    //cerr << "DUD err" << endl;
#endif
    return OK;
  }
}

int uc_eval(char *expr, bool append_semicolon=true, bool synchronous=false, char *name=NULL, int lineno=0)
{
 static char buffer[EXPR_BUFF_SIZE];
 char *ibuff = synchronous ? new char[strlen(expr)+10] : buffer;
 strcpy(ibuff,expr);
 if (append_semicolon) strcat(ibuff,";");
 if (synchronous) strcat(ibuff,"\n#q");
 //cout << "*ibuff " << ibuff;
 Parser::state.lineno = 0;   // reset error state and go for it...
 Parser::state.file = "";
 bool old_compile_flag = Parser::debug.compile_program;
 Parser::debug.compile_program = false;
 Input::insert_stream(new istrstream(ibuff),name ? name : "$EXPR",lineno);
 if (synchronous) { 
     int ret = call_parser();
     delete ibuff;
     Parser::debug.compile_program = old_compile_flag;
     if (ret == OK) { 
         if (Engine::paused()) return HALTED;
         if (Parser::state.lineno > 0) return CRASHED;
     }
     return ret;
 } 
 else {
  Parser::debug.compile_program = old_compile_flag;
  return PENDING;
 }
}

// *change 1.2.8 May be passed an optional (file,position) for
// incremental compilation.  In this mode, we force compilation
// to occur in global context.
int redirected_eval(char *s, bool do_semi, char *name=NULL, int lineno=0)
{
  Table* tbl = &Parser::state.context();
  bool pushing_context = false;
  if (name != NULL && tbl != &Parser::global()) {
      Parser::state.push_context(&Parser::global());
      pushing_context = true;
  }
// synchronous execution
  Errors::redirect_output(true);
  int ret = uc_eval(s,do_semi,true,name,lineno);
  Errors::check_output(); // make sure errors & messages no longer redirected
  if (pushing_context) {  // restore context
      Parser::state.pop_context();
      if (ret == HALTED) ret = OK;  // engine state was halted; no problem
  }
  return ret;
}

void save_parser_state();
void restore_parser_state();

// *add 1.2.4 exported as uc_exec()
int _uc_exec(char* s, void* cntxt, char* filename, int line)
{
  string file;
  int lineno;
  // protect the temporary code block from being trashed
  Instruction* immed_code = Parser::immediate_code_ptr();
  Parser::immediate_code_ptr(NULL); 
  // redirect errors and messages
  Errors::reset_error_state();
  Errors::redirect_output(true);

  // keep old parser state for later
  Parser::ParserState old_state = Parser::state;
  dcl_set(false,false); // save the DCL & ALS stacks...
  save_parser_state();  // save the BISON state...

  // do the compilation/evaluation, using the context if requested.
  if (cntxt) Parser::state.push_context((Table*)cntxt);
  int ret = uc_eval(s,false,true,filename,line);
  if (cntxt) Parser::state.pop_context();

  // reset parser state, but make sure the error state is kept!
  file = Parser::state.file;
  lineno = Parser::state.lineno;
  restore_parser_state();
  dcl_reset();
  Parser::state = old_state;
  Parser::state.file = file;
  Parser::state.lineno = lineno;
  // make sure errors & messages no longer redirected
  Errors::check_output(); 
  Parser::immediate_code_ptr(immed_code);
  return ret;
}

// *add 1.2.4 exported as uc_result()
void _uc_result(int ret, char *output, int sz, char* filename, int* line)
{
  char *out;
  if (ret==OK) out = Errors::get_redirect_buffer(1);
  else out = Errors::get_redirect_buffer(2);
  strncpy(output,out,sz);
  if (filename) {
    *line = Errors::get_stop_position(filename);
  }
}

int ext_uc_eval(char *expr, char *output, int sz)
{
  int ret = redirected_eval(expr,true);
  _uc_result(ret,output,sz,0,0);
  return ret;
}

bool s_single_step = false;
static char* gCurrentArgs = "";

int ext_run_program(char *cmdline, int stepping)
{
 Errors::redirect_output(true,false);
 char buff[256];
 strcpy(buff,cmdline);
 if (stepping) { Engine::set_single_stepping(stepping == 1); } // *HACK*
 if (Program::run(buff)) return 0;  // main() not found!
 else return 1;
}

bool insert_input_file(char *filename)
{
  ifstream *pfs = new ifstream(filename,IOS_IN_FLAGS); 
  if (!(*pfs)) {
        delete pfs;
        return false;
  }
  Input::insert_stream(pfs,filename);
  return true;
}

int safe_atoi(char *str)
{ 
  return str==NULL ? 0 : atoi(str);
} 

bool command_error(char *msg, char *sym=NULL)
{
  cerr << msg;
  if (sym != NULL) cerr << '\'' << sym << '\'';
  cerr << endl;
  return true;
}

bool cant_open_err(char *cfile)
{ return command_error("Cannot open",cfile); }

bool cant_find(char *name)
{  return command_error("Cannot find",name); }


static char *TMPFILE = "_tmp010_u.txt";

void uc_system(char *cmd)
{
#ifndef _WCON
  system(cmd);
#else
  int exec(char *msg, int cshow, bool do_wait);
  const int BUFFSIZE = 200;
  char buff[BUFFSIZE];
  // The idea here is to direct output to a temporary
  // (if not already redirecting!) and dump this file.
  string spath = getenv("COMSPEC");   // Win32 warning...
  string cmdl = spath + " /c " + string(cmd);
  bool redirect_stdout = cmdl.find(">") == -1;
  if(redirect_stdout) { cmdl += " > "; cmdl += TMPFILE; }
  exec(cmdl.c_str(),0,true);
  if(redirect_stdout) {
    ifstream inf(TMPFILE,IOS_IN_FLAGS);
    if (!inf) return;
    while (!inf.eof()) {
      inf.getline(buff,BUFFSIZE);
      puts(buff);
     }
  }
#endif
}

char *get_temp_log()
{
 static char buff[30];
 struct tm *ts;
 time_t t;
 time(&t);
 ts = localtime(&t);
 ts->tm_mon++;  
 sprintf(buff,"%02d%02d-%02d%02d",ts->tm_mon,ts->tm_mday,ts->tm_hour,ts->tm_min);
 return buff;
}

// *add 1.2.1 Getting interactive help
string uc_home_dir(); // forward

void show_help(char* help_text, char* cmd, char marker)
{
    char buff[256];
    string help_file = uc_home_dir()+"/";
    help_file += help_text;
    ifstream in(help_file.c_str(),IOS_IN_FLAGS);
    if (! in) { cerr << "cannot find " << help_file << endl; return; }
    if (cmd && *cmd == marker) cmd++;
    while (! in.eof()) {
      in.getline(buff,sizeof(buff));
      if (buff[0] == marker) {
       if (! cmd) cmsg << buff << endl;
       else {         
         if (strncmp(cmd,buff+1,strlen(cmd))==0) {
            buff[0] = ' ';
            while (buff[0] != marker) {
                cmsg << buff << endl;
                in.getline(buff,sizeof(buff));
            }
         return;
         }
       }
      }
    }  
}
//* #endif

// *change 1.2.8 added more control on whether one wants the contents of the parent 
// context as well.
void display_locals(Table *tbl, void *pobj, bool is_ptr = false, bool do_all = false)
{
// *add 1.1.2 Will dereference a pointer or reference variable
  const int LOCALBUFF = 1024;
  char buff[LOCALBUFF];
  bool pushing_context = tbl != NULL;
  if (!pushing_context) tbl = &Parser::state.context();
  else {
      Parser::state.push_context(tbl);
	  if (is_ptr) pobj = *(void **)pobj;
      Engine::object_ptr(pobj);
  }
  ostrstream os(buff,LOCALBUFF);
  int flags = Table::SEMICOLON_SEP | Table::VARS;
  if (do_all) flags |= Table::ALL;
  tbl->dump_entries(os,flags);
  os << ends;
  
  Parser::debug.no_access_control = true;
  uc_eval(buff,false,true);
  Parser::debug.no_access_control = false;
  if (pushing_context) Parser::state.pop_context();
}

/// These guys bracket every LOAD operation; the idea here
/// is to clean up any namespace pollution so that
/// the module compilation won't be bothered.  Afterwards,
/// the crud is put back in.
/// *add 1.2.0 Any files with extension ".c" are loaded in C-mode
/// *add 1.2.3 Ditto, files with extension .c, .cpp or .cxx loaded in _strict mode_
/// This will attempt to enforce the standard language as much as possible.
/// *add 1.2.9 Managing the program ODL (Object Destructor List)
/// The basic assumption around here is that compile_module() is _not_ reentrant;
/// e.g. using #l within a file loaded with #l is not guaranteed to work properly.

static bool s_last_c_mode, s_last_strict_mode, s_is_cpp_source = false, s_is_conditional;

// only called if the file could be opened...
int module_open()
{ 
    string ext = Utils::file_extension(Module::current()->name());
    s_is_cpp_source = ext == ".c" || ext == ".cpp" || ext == ".cxx";
    s_last_c_mode = Parser::debug.c_mode;
    s_last_strict_mode = Parser::debug.strict;
    if (ext == ".c") Parser::set_c_mode(true);     
    Parser::debug.strict = s_is_cpp_source;
    Parser::debug.compile_program = s_is_cpp_source;
    Module::clean_namespaces();
    Module* pm = Module::current();
    pm->clean_macros_and_typedefs();
    // *change 1.2.9a Plain #l works as before; #lc creates __init functions etc.
    s_is_cpp_source = s_is_cpp_source && s_is_conditional;
    LoadedModuleList::init_module(s_is_cpp_source? pm->id() : -1);
    return 0;
}

// should always called!
int module_close()
{
    using namespace Parser;
    if (s_is_cpp_source ) {
      Instruction* pi;
      if (code().total_instructions() > 0) {
          code().emit_return(t_void);
          pi = code().end_code();
      } else pi = NULL;
      LoadedModuleList::finish_module(pi);      
    }
    s_is_cpp_source = false;
    Module::restore_namespaces();
    Parser::set_c_mode(s_last_c_mode);
    Parser::debug.strict = s_last_strict_mode;
    Parser::debug.compile_program = false;
    return 0;
}

static void set_on_open_restore(bool is_conditional)
{
   s_is_conditional = is_conditional;
   Input::set_open_restore(module_open,module_close);
}

extern Function* gLastFunction;  //*DEBUG - found in common.cpp

void __mangle()
{
 cerr << '_' << Mangle::GCC3(gLastFunction) << ';' << endl;
}

enum {CANT_OPEN, FILE_UNCHANGED, SUCCESS};

int compile_module(char *file, bool is_conditional)
{
// *fix 1.2.8 Make sure we're in global context before attempting a compilation
    bool is_break = Parser::current_function() != NULL;
    if (is_break) Parser::state.push_context(&Parser::global());
// *add 0.9.8 #l should now do a dependency check to allow changed headers to be included...
    Module *pm = Module::from_name(file);   
    if (!is_conditional) {
      if (pm != NULL) {
        Module::reset_modify_flags();
       // should we _always_ reload with #l?
       if (! pm->needs_rebuilding()) return FILE_UNCHANGED;
      }
    } else if (pm != NULL && ! pm->needs_rebuilding()) return FILE_UNCHANGED;
    //*DEBUG*
    if (is_conditional) {
      cmsg << "load: " << file;
      if (pm != NULL) cmsg << " id = " << pm->id();
      cmsg << endl;
    }
    set_on_open_restore(is_conditional);
    Parser::state.reset();
    if (is_break) Parser::state.pop_context();
    return Input::open(file) ? SUCCESS : CANT_OPEN;
}

int yylex(); // further along here...


extern bool tok_dbg;

bool UCTokenStream::user_cmd(string ppd)
{ 
  static string current_file;
  if (ppd == "q") return false; else
#ifdef _WCON
  if (ppd == "ql") { 
    wcon_copy_to_log(get_temp_log());
    return false;
  } else
#endif
  // *add 1.1.2 #pragma! Just dlink and pack()
    // *fix 1.2.0 it's not an error to leave out the library name
  if (ppd == "pragma") {	   
	  char *line = get_upto(0,false);
	  string what = strtok(line," ()");
	  char *arg   = strtok(NULL," ()");
	  if (what == "dlink") {
         if (!Builtin::set_current_lib_file(arg)) return cant_open_err(arg);
         char *impf = strtok(NULL," ()");
         if (impf != NULL) Builtin::generate_ordinal_lookup(impf);
      } else
	  if (what == "pack") Parser::set_alignment(atoi(arg));
#ifdef _DEBUG
      else
      if (what == "break") {
        gDebugBreak = atoi(arg);
        if (gDebugBreak == 1) {
          __break(1);
          gDebugBreak = 0;
        }
      } 
#endif
  } else
  if (ppd == "x") uc_system(get_upto(0,false)); else
  if (ppd == "cd") {
     Utils::change_dir(Input::next_token(true));
  } else
  if (ppd == "pwd") {
     cmsg << Utils::get_curr_dir() << endl;      
  } else
  if (ppd == "l" || ppd == "lc") {
    Errors::reset_error_state();
    char *cfile = Input::next_token();
    if (cfile) current_file = cfile;
    else if (current_file[0] != '\0') cfile = current_file.c_str();
    else return command_error("Please supply file\n");
    bool condn = ppd == "lc";
    int ret = compile_module(cfile,condn);
    
    if (ret == CANT_OPEN && !condn) cant_open_err(cfile);
    else if (ret == FILE_UNCHANGED) cmsg << "unchanged\n"; 
  } else 
  if (ppd == "bl") {
   // *add 0.9.4 Use this before a series of #lc commands...
    Module::reset_modify_flags();
  } else
 // *add 1.2.8 the #args command can be used to set commmand-line args for debugging;
 // after #args, the #r commands will use it as the default, unless overriden.
  if (ppd == "r" || ppd == "rx" || ppd == "args") {
     char* args = get_upto(0,false);
     if (ppd=="args") gCurrentArgs = strdup(args);
     else { // run the program (rx means 'don't run in separate thread'
       if (*args == '\0' && gCurrentArgs != NULL) args = strdup(gCurrentArgs);
       if (!Program::run(args,ppd=="rx")) cant_find("main");
     }
  } else
#ifdef _WCON
  if (ppd == "log") {
     wcon_copy_to_log(Input::next_token());
  } else
#endif
  if (ppd == "lib") {
    char *libf = Input::next_token();
    if (!Builtin::set_current_lib_file(libf)) cant_open_err(libf);
    char *impf = Input::next_token(false);
    // *fix 1.2.5 we were attempting to read an ordinal file after an empty #lib
    if (libf) {
      if (impf != NULL) Builtin::generate_ordinal_lookup(impf);
    }
  } else
  if (ppd == "opt") {
     char *line = get_upto(0,false);
     char *opt = strtok(line,", ");
     while (opt) { 
      char ch = *opt++;
      bool is_on = *opt == '+';
      bool strip_prompt, block_ide, change_block_ide = false;
      switch(ch) {
      case 'u': Parser::debug.auto_dissemble = is_on; break;
      case 'd': Parser::debug.dump_expr = is_on; break;
      case 'x': Parser::debug.auto_exec = is_on; break;
      case 't': Parser::debug.function_trace = is_on; break;
      case '4': Parser::debug.class_dword_align = is_on ? 4 : 1; break;
      case 'v': Parser::debug.verbose = is_on; tok_dbg = is_on; break;
      case 's': Parser::debug.strict = is_on; break;
      case 'p': Parser::debug.ptr_check = is_on; break;
      case 'a': Parser::debug.no_access_control = is_on; break; 
      case 'c': strip_prompt = is_on; break;
      case 'q': block_ide = is_on; change_block_ide = true; break;
      case 'C': Parser::set_c_mode(is_on); break;
      case 'T': Parser::debug.use_typedef_names = is_on; break;
      case 'L': Parser::debug.suppress_link_errors = is_on;  break;
      case 'i': Parser::debug.interactive_debugging = is_on; break;
      case 'S': Parser::debug.no_trace_std = is_on; break;
      case 'e': Parser::debug.errors_as_break = is_on; break; 
      case 'R': Parser::debug.range_check = is_on;  break;
      // temporary debugging options go here...
      }
      opt = strtok(NULL,", ");     
#ifdef _WCON
      // defaults initially to being on....
      if (strip_prompt) wcon_prompt_char(';',0);
                  else wcon_prompt_char(0,0);
      if (change_block_ide) IDE::set_blocking(block_ide);
#endif
    }
  } else
  if (ppd == "lv") {
    display_locals(NULL,NULL,false,Input::next_token() != NULL);
  } else
// *add 1.2.8 #dl <obj> shows only _local_ fields, not inherited as well.
    if (ppd == "d" || ppd == "dl") { 
    PEntry pe = Input::lookup_next_symbol(); 
    if (pe == NULL)  return cant_find("symbol");
    Type t = pe->type;
    if (!t.is_class()) return command_error("Not an object");
    void *ptr = pe->global_ptr();
    display_locals(t.as_class(),ptr,t.is_ref_or_ptr(), ppd == "d");
  } else
  if (ppd == "rm") { // remove symbol or program
     PEntry pe = Input::lookup_next_symbol();
     if (pe->type.is_function()) { // this was a function
        if (pe->name=="main") Breakpoint::remove_all();  // *add 0.9.8 remove program and clear out breakpoints
        FunctionEntry* pfe = reinterpret_cast<FunctionEntry *>(pe->data);
        int imod = pfe->back()->line_nos()->module();
        Module *pm = Module::from_id(imod);
        if (pm != NULL) Module::remove(pm);
     } 
     if (pe == NULL) return cant_find("symbol");
     Parser::global().remove(pe);     
  } else
  if (ppd == "s") { // *add 0.9.4 Now can stop runaway programs w/ #s
#ifdef _WCON
     Program::stop_main(false); // *change 1.2.8 no longer an error
#endif
  } else
  if (ppd == "st" || ppd == "sto") { // *add 1.1.4 Single-stepping through programs!
      s_single_step = true;  
      Engine::set_single_stepping(ppd == "st");      
      Program::run(gCurrentArgs,false /*Input::next_token() == NULL*/);
  } else
  if (ppd == "clr") { // clear input (0) or console (1)
#ifdef _WCON
      wcon_clear(safe_atoi(Input::next_token()));
#endif 
// otherwise need some portable way of doing clr 0?
  } else
  if (ppd == "attach") {
    //	  char *fn = Input::next_token();
    //	  attach_main_context(fn); 
  } else
  if (ppd == "mod") {
      char *fun = Input::next_token();
      if (fun == NULL) Module::dump_entries(cmsg,0);
      else {
         // Module *pm = Module::from_name(file);
         // if(pm == NULL) cant_find(file);
         // else pm->dump(*msg,Module::FUNS | Module::DEPEND);
         string file;
         // *change 0.9.4  #mod name gives defn pos. of function 'name'
         int ln = Module::file_from_function(Function::lookup(fun),file);
         cmsg << file << ' ' << ln << endl;
      }
  } else
  if (ppd == "types" || ppd == "funs") {
	  char *pat = Input::next_token();
	  EntryList el;
	  if (pat == NULL) pat = "*";
	  Parser::global().search_entries(pat,&el,(ppd == "funs") ? FUNCTIONS : (CLASSES | NAMESPACES | TYPEDEFS));
	  EntryList::iterator eli;
	  FORALL(eli,el) cmsg << (*eli)->name << endl;
  } else
  if (ppd == "gt" || ppd == "b") {
      Errors::reset_error_state();
      int lineno = safe_atoi(Input::next_token());
      char *file = Input::next_token(false);
      if (file == NULL) file = current_file.c_str();  // set by #l, etc
      //cout << "b " << file << ':' << lineno << endl;
      Breakpoint::toggle(file,lineno,ppd == "b",cmsg);
  } else
  if (ppd == "bs") { // set a number of breakpoints in a file   
     Errors::reset_error_state();
     char *tok, *file = Input::next_token();
     int lines[10], i = 0;
     while ((tok = Input::next_token(false)) != NULL)
         lines[i++] = safe_atoi(tok);
     if (i == 0) { // this is a _request_!!
         Breakpoint::group(file,lines,i,true);
         cmsg << i << ' ';
         for (int k = 0; k < i; k++) cmsg << lines[k] << ' ';
         cmsg << endl;
     } else
     Breakpoint::group(file,lines,i,false);
  } else
  if (ppd == "ff") {
      Engine::set_frame(safe_atoi(Input::next_token(true)),true);
      if (! Parser::debug.interactive_debugging)
        display_locals(NULL,NULL);
  } else
  if (ppd == "fs") {
    dump_fun_stack();
  }
  else
  if (ppd == "u" || ppd == "mm" || ppd == "fe") {
    // char *name = next_token();
     PEntry pe = Input::lookup_next_symbol();
     if (pe == NULL) return true; 
     int idx = safe_atoi(Input::next_token(true));
     if (pe->type.is_function()) {
         if (ppd == "u") Parser::dump_function(pe,true,idx,cout);        
         else {
           Function *pf = *(((FunctionEntry *)pe->data)->begin());
           if (ppd == "fe") {
             FBlock *fb = pf->fun_block();
             fb->nlocal = idx;
           } else {
        //    cout << "MS:  " << Mangle::microsoft(pf) << endl;
			cout << Mangle::GCC3(pf) << endl;
           }
         }
     } 
  } else
  if (ppd == "v") { // inspect - used to be 'var'!!
     PEntry pe = Input::lookup_next_symbol();
     if (pe==NULL) return true;
     Parser::dump_var(pe);
  } else
  if (ppd == "tx") { //*TEST*
      redirected_eval(get_upto(0,false),false);
  }
  else
  if (ppd == "rx") { //*TEST*
     ext_run_program(get_upto(0,false),false);
  }
  else
  if (ppd == "vmt") {
    PEntry pe = Input::lookup_next_symbol(); 
    if (pe == NULL)  return true;
    Type t = pe->type;
    int *ptr = *(int **)pe->global_ptr();
    ptr--;  // now at hidden pointer...
    PClass pc = t.as_class();
    if (! pc->has_VMT()) cout << "No VMT\n";
    cout << "name " << pc->name() << " " << pc->last_slot() << endl;         
	for(int i = 1; i < pc->last_slot(); i++) {
	   cout << i << ' ';
	   ((Function *)ptr[i])->dump(cout);
	   cout << endl;	  
    }
  }
  else 
  if (ppd == "unload") {
      Builtin::unload_lib(Builtin::get_dll_handle());
  }
  else
  if (ppd == "mc") { macro_cleanup(); clear_global_namespace(); }
  else
  if (ppd == "unwind") { Engine::global_unwind(); }
  else // *add 1.2.9 Module trace back list
  if (ppd == "trace_back") { dump_module_traceback(); }
  else
  // *add 1.2.1 Interactive Help
  if (ppd == "help") show_help("help.txt",Input::next_token(true),'#');
  else
  if (ppd == "li") Function::lookup(Input::next_token(true))->line_nos()->dump(cmsg);
    // *fix 1.2.1 (Eric) Complain if this command is not recognized
  else 
     cerr << '\'' << ppd << "\' is not a command or preprocessor statement" << endl;
  return true;
}

/*static*/ int mInteractiveMode = 1;
static string mUCDir;
static char* mPgmName;

bool interactive_mode()  { return mInteractiveMode; }
string uc_home_dir()     { return mUCDir; }

void bail_out()
{
  check_error();
  exit(-1);
}

int setup_lib()
{
 Parser::init_lib();
 return 0;
}

char *uc_get_title()
{ return mUCTitle; }

void Main::initialize()
{
  Exception::initialize();
  Keywords::init();
  Parser::init();
  Operators::init();
  Expressions::init();
  UCTokenStream::init();
#ifdef _WCON
  IDE::initialize();
 // Non-Interactive
  mInteractiveMode = true;
#endif
}

#ifdef _DEBUG
void init_mem_check() // *DEBUG*
{
    static bool init = false;
    if (! init) { 
        init = true;
// *DEBUG* Fine control of leak detection
// Get current flag
     int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );

// Turn on leak-checking bit
  //  tmpFlag |= _CRTDBG_LEAK_CHECK_DF;

// Turn on check-always bit
     tmpFlag |= _CRTDBG_CHECK_ALWAYS_DF;

     tmpFlag |= _CRTDBG_DELAY_FREE_MEM_DF;

// Turn off CRT block checking bit
//   tmpFlag &= ~_CRTDBG_CHECK_CRT_DF;

// Set flag to the new value
    _CrtSetDbgFlag( tmpFlag );
    }
}


int check_mem() //  *DEBUG*
{
  init_mem_check();
  return _CrtCheckMemory();
}
#endif

// *add 1.1.2 Please note that this does NOT work under Linux!
char *Main::uc_exec_name()
{
  return mPgmName;
}

string Main::uc_lib_dir()
{
  return uc_home_dir() + "/lib/";
}

bool add_include_path(string path)
{
  Utils::check_path_end(path);
  if (! Utils::can_access(path)) {
     error("Cannot find include directory " + path);
	 return false;
  }
  TokenStream::set_include_dir(path.c_str());
  return true;
}

bool extract_options(int *argc, char **argv, char *opt, char **optstring)
{
  char *p = argv[1];
  if (p && p[0]=='-') {
     *opt = p[1];
     *optstring = p+2;
	 for (int i = 1; i < *argc; i++) argv[i] = argv[i+1];
	 --(*argc);
	 return true;
  } else return false;
}

// *add 1.2.2 -D adds a preprocessor macro, in the usual way (there must be NO space before the NAME=SUBST)
bool add_prepro_macro(char *str)
{
  char *name = strtok(str," =");
  if (! name) return false;
  char *subst = strtok(NULL," =");
  TokenStream::macro_subst(name,subst ? subst : "");
  return true;
}

// *add 1.1.4 The -I option will add another include path....
// *add 1.1.4
//  Environment var UC_DEFAULT_ARGS can contain extra stuff
//  which will be appended to the commmand line passed to UC
// *fix 1.1.4W File names on the WCON command line can now contain spaces
// *change 1.2.0 -O switches off over-allocation
//  Pointer check is no longer on by default in release build; use -P.
//  -W suppresses dynamic link warnings; it now switches off verbose import information as well.


bool Main::process_command_line(int& argc, char**& argv)
{
  char *paths[MAX_INCLUDE_PATHS];
  int ipaths = 0;
  bool force_interactive = false;
  char *inc_path = NULL;
  bool use_gtk = false;
  char *extra_args = getenv("UC_DEFAULT_ARGS");
  Utils::Args args(argc,argv);
  args.set_stop_after_file();

  if (extra_args && argv) {
    argv = args.append_extra_args(extra_args);
  }

  special_init(&argc,&argv,use_gtk);

  // under Win32, we try to deduce where we're called from using argv[0]
#ifdef _WIN32
  if (argv != NULL) {
    char *progname = argv[0];
    mPgmName = strdup(progname);
    Utils::strip_last(progname);  // strip off the '\ucw.exe'
#ifdef _DEBUG
    Utils::strip_last(progname);   // strip off the 'wdebug' 
    Utils::strip_last(progname);   // strip off the 'src'
#endif         
    mUCDir = argv[0];
   }
#endif

   // UC_HOME is determined by evaluating all of the following. The
   // last one that is set is used as the final value.
   //
   //  - the compiled in default
   //    (under Win32 we get this from argv[0])
   //  - the UC_HOME environment variable
   //  - the -H option
   
   // *fix 1.2.8 don't use the UC_HOME macro under Win32; rely on argv[0]
   // for the initial guess.

#if defined(UC_HOME) && ! defined(_WIN32)
    inc_path = UC_HOME;
#else
  // *fix 1.2.9 argv==NULL when this routine is called in the DLL version!
    if (argv) mUCDir = argv[0];
#endif

  char *t;
  if (t=getenv("UC_HOME")) {
    // the value set in the environment overrides the compiled in default
    inc_path = t;  // *fix 1.0.0L bash doesn't like 'UC-INC'
  }
  else
  {
	  
	  

	  

  }

  string path;  
  if (inc_path==NULL) {
#ifdef WIN32    
   path = mUCDir;
#endif
  } else { 
    path = inc_path;
    mUCDir = inc_path; // *fix 1.0.0L UC_HOME must override this!
  }
 
  if (argv != NULL) {
    char opt;
    while (args.get_opt(&opt)) {
      switch(opt) {
      // the command line option overrides any previously set value	
      case 'H': path = mUCDir = args.get_opt_parameter(); break;  // *add 1.1.3 Can override UC_HOME
      case 'I': 
          if (ipaths == MAX_INCLUDE_PATHS) error("Exceeded available include paths");
          else paths[ipaths++] = args.get_opt_parameter();
          break;
      case 'G': use_gtk = true; break;
      case 'O': Parser::debug.do_overalloc ^= 1; break;
      case 'P': Parser::debug.ptr_check = true; break;
      case 'W': Parser::debug.suppress_link_errors = true; break;
      case 'D': add_prepro_macro(args.get_opt_parameter()); break;
      case 'i': force_interactive = true; break;
      case 'F': Parser::debug.attempt_inline = true; break;  // *add 1.2.3a New Optimization flag
// *add 1.2.5 run in specified directory
      case 'r': Utils::change_dir(args.get_opt_parameter()); break;
// *add 1.2.5 switch on range checking of arrays and vectors          
      case 'R': Parser::debug.range_check = true;            break;
// *add 1.2.6 Outputing version and help
//* #ifdef _CONSOLE
      case 'v': cout << "UnderC " << mUCVersion << endl; return false;
      case '-': 
         if (strcmp(args.get_opt_parameter(),"help")==0)
         {
             show_help("cmd-help.txt",NULL,'-');
             return false;
         }
      break;
      // *add 1.2.9 Evaluating expressions from the command-line
      case 'e':
          uc_eval(args.get_opt_parameter(),true,true);
          exit(0);
          break;
//* #endif
// *add 1.2.6 These are also available as #opt parameters
      case 'T': Parser::debug.use_typedef_names = true;     break;
      case 'S': Parser::debug.skip_method_bodies = false; break; 
      default: cerr << "unrecognized option: " << opt << endl; return false;
      }
    }
    if (Parser::debug.range_check) add_builtin_macro("_RANGE_CHECK");
  }

// *change 1.2.2b The Windows GUI build is _always_ interactive, and the console
// build is interactive if (a) there are no args or (b) forced with the -i option.
#ifdef _WCON
  mInteractiveMode = force_interactive || argc == 1; // true;
#else
  mInteractiveMode = force_interactive || argc == 1;
#endif

 Utils::check_path_end(path);
 path += "include";
 if (! add_include_path(path)) return false;

// NOW we may add the extra paths, if any
 for(int i = 0; i < ipaths; i++)
	  add_include_path(paths[i]);

 return true;
}

void Main::banner()
{
#ifdef _WCON
    wcon_set_size(20,50,600,500);
    wcon_set_title(mUCTitle);
    wcon_prompt_char(';',0);
#endif
    cmsg << "UnderC C++ Interpreter vs " << mUCVersion << endl;
    cmsg << "Steve Donovan, 2001-2003" << endl;
    cmsg << "This program is GPL'd; see LICENCE for details" << endl;
}

void Main::finalize()
{
// *add 1.2.3 Destroy any statically allocated objects
   Engine::global_unwind();

// and unload any DLLs
   Builtin::finis();  

#ifdef _WCON
// *add 1.2.8 inform the IDE that we're going down... 
   IDE::ucw_shut_down();
#endif
}

int Main::interactive_loop()
{
  for(;;) {
    if (call_parser()==FAIL) {
   //*  if (! interactive_mode()) return -1;// bail out if in batch mode
    } else return 0;// successful exit!
  }  
}

////////// MAIN ///////////////
#ifndef _USRDLL
int main(int argc, char **argv)
#else
int pgm_main(int argc, char **argv)
#endif  
//----------------------------
{
  using namespace Main;

  initialize();

 
  // *fix 1.2.3 Suppress irritating warning until we can decide what to do here!
  if (! process_command_line(argc, argv)) return 0;

  if (interactive_mode()) {    
    banner();
    Input::open("CON");
    Input::set_open_restore(NULL,setup_lib);
    const char* defs_file = (argc > 1) ? argv[1] : "defs.h";
    if (!Input::open(defs_file)) {
	   cmsg << "Now opening defs.h in UC home directory" << endl;
	   string uc_home = uc_home_dir();
       Utils::check_path_end(uc_home);
       string default_defs = uc_home + "defs.h";
       if (! Input::open(default_defs.c_str())) check_error();
    }  
  } else {
      // *add 1.2.9 Important to call these before/after routines
      // for initialization/finalization code to work properly.
      // *change 1.2.9a old behaviour
      set_on_open_restore(false);
      if (! Input::open(argv[1])) bail_out();
  }

// keep going until we end successfully!
  int retval = interactive_loop();
  if (retval == -1) return retval;
  if (! interactive_mode()) {
    mInteractiveMode = true;  // to force statement_end() to evaluate!
	Parser::init_lib();       // *fix 1.1.0 force any library initializations
    retval = Program::call_main(argc-1,argv+1,Program::new_window);
  }

  finalize();
  return retval;

}


